package cn.seaboot.admin.mvc.response;

import cn.seaboot.admin.mvc.Problem;
import cn.seaboot.commons.core.FastJsonUtils;
import cn.seaboot.commons.exception.BizException;
import cn.seaboot.commons.exception.ServiceException;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;

/**
 * Controller异常切面，处理在Controller抛出的异常
 *
 * @author postm
 */
public class FeedbackJson implements Feedback {

    public static final String EXEC_IO = "数据传输异常！";
    public static final String EXEC_SQL = "数据查询异常！";
    public static final String EXEC_ARGUMENT = "请求参数异常！";


    /**
     * Handler IllegalArgumentException
     *
     * @param request  -
     * @param response -
     * @param ex       IllegalArgumentException
     * @return Problem
     */
    @Override
    public Problem handlerIllegalArgumentException(HttpServletRequest request, HttpServletResponse response, IllegalArgumentException ex) {
        return Problem.builder()
                .withStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                .withTitle(ex.getMessage())
                .withInstance(request.getServletPath())
                .build();
    }

    /**
     * Handler BizException
     *
     * @param request  -
     * @param response -
     * @param ex       BizException
     * @return Problem
     */
    public Problem handlerBizException(HttpServletRequest request, HttpServletResponse response, BizException ex) {
        return Problem.builder()
                .withStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                .withTitle(ex.getMessage())
                .withInstance(request.getServletPath())
                .build();
    }

    /**
     * Handler ServiceException
     *
     * @param request  -
     * @param response -
     * @param ex       ServiceException
     * @return Problem
     */
    @Override
    public Problem handlerServiceException(HttpServletRequest request, HttpServletResponse response, ServiceException ex) {
        return Problem.builder()
                .withStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                .withTitle(ex.getMessage())
                .withInstance(request.getServletPath())
                .build();
    }

    /**
     * Handler MissingServletRequestParameterException
     *
     * @param request  -
     * @param response -
     * @param ex       MissingServletRequestParameterException
     * @return Problem
     */
    @Override
    public Problem handlerMissingServletRequestParameterException(HttpServletRequest request, HttpServletResponse response, MissingServletRequestParameterException ex) {
        return Problem.builder()
                .withStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                .withTitle(ex.getMessage())
                .withInstance(request.getServletPath())
                .build();
    }

    /**
     * Handler FileException
     *
     * @param request  -
     * @param response -
     * @param ex       FileException
     * @return Problem
     */
    @Override
    public Problem handlerSQLException(HttpServletRequest request, HttpServletResponse response, SQLException ex) {
        return Problem.builder()
                .withStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                .withTitle(EXEC_SQL)
                .withInstance(request.getServletPath())
                .build();
    }

    /**
     * Handler FileException
     *
     * @param request  -
     * @param response -
     * @param ex       FileException
     * @return Problem
     */
    @Override
    public Problem handlerIOException(HttpServletRequest request, HttpServletResponse response, IOException ex) {
        return Problem.builder()
                .withStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                .withTitle(EXEC_IO)
                .withInstance(request.getServletPath())
                .build();
    }


    /**
     * Handler ConstraintViolationException (from: javax.validation)
     *
     * @param request  -
     * @param response -
     * @param ex       Exception
     * @return Problem
     */
    @Override
    public Problem handlerConstraintViolationException(HttpServletRequest request, HttpServletResponse response, ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> set = ex.getConstraintViolations();
        Map<String, Object> object = new HashMap<>(set.size());
        for (ConstraintViolation<?> violation : set) {
            object.put(violation.getPropertyPath().toString(), violation.getMessage());
        }
        return Problem.builder()
                .withStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                .withTitle(EXEC_ARGUMENT)
                .withDetail(FastJsonUtils.toJSONString(object))
                .withInstance(request.getServletPath())
                .build();
    }

    /**
     * Handler BindException (from: hibernate-validator)
     *
     * @param ex BindException
     * @return Problem
     * @see BindException
     */
    @Override
    public Problem handlerBindException(HttpServletRequest request, HttpServletResponse response, BindException ex) {
        BindingResult result = ex.getBindingResult();
        List<InvalidEntity> entities = new ArrayList<>();

        List<ObjectError> errors = result.getAllErrors();
        for (ObjectError error : errors) {
            entities.add(new InvalidEntity(error.getObjectName(), error.getDefaultMessage()));
        }

        return Problem.builder()
                .withType("00001")
                .withStatus(HttpStatus.BAD_REQUEST)
                .withTitle("Validation Failed")
                .withDetail("One or more fields failed validation.")
                .withInstance(request.getServletPath())
                .withExtensions(entities).build();
    }

    /**
     * Handler MethodArgumentNotValidException (from: spring-bind)
     *
     * @param request  -
     * @param response -
     * @param ex       Exception
     * @return Problem
     */
    @Override
    public Problem handlerMethodArgumentNotValidException(HttpServletRequest request, HttpServletResponse response, MethodArgumentNotValidException ex) {
        BindingResult result = ex.getBindingResult();
        List<FieldError> errors = result.getFieldErrors();
        Map<String, Object> object = new HashMap<>(errors.size());
        for (FieldError error : result.getFieldErrors()) {
            object.put(error.getField(), error.getDefaultMessage());
        }
        return Problem.builder()
                .withStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                .withTitle(EXEC_ARGUMENT)
                .withDetail(FastJsonUtils.toJSONString(object))
                .withInstance(request.getServletPath())
                .build();
    }

    /**
     * Handler AccessDeniedException (from: security)
     *
     * @param request  -
     * @param response -
     * @param ex       Exception
     * @return Problem
     */
    @Override
    public Problem handlerAccessDeniedException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) {
        return new Problem(null, HttpStatus.FORBIDDEN, HttpStatus.FAILED_DEPENDENCY.getReasonPhrase(), null, null, null);
    }

    /**
     * Handler Exception
     *
     * @param request  -
     * @param response -
     * @param ex       Exception
     * @return Problem
     */
    @Override
    public Problem handlerException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        return new Problem(null, HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), null, null, null);
    }
}
